﻿
using System;
using System.Collections.Generic;
namespace AE.Remoting {

    public class ServiceQueue<T> : IDisposable {

        public event Action<Exception> OnException;

        private ServicePool p_Pool;
        private long p_CurrentlyExecuting;
        private Queue<T> p_Queue = new Queue<T>();
        private System.Threading.AutoResetEvent p_Notifier = new System.Threading.AutoResetEvent(false);
        private Action<T> p_Action;
        private bool p_IsDisposed = false;


        public ServiceQueue(Action<T> action, int numThreads, string name, System.Threading.ThreadPriority priority) {
            p_Action = action;
            p_Pool = new ServicePool(ProcessQueue, numThreads, name, priority);
        }

        public void Enqueue(T obj) {
            lock (p_Queue) {
                p_Queue.Enqueue(obj);
            }
            p_Notifier.Set();
        }

        public long QueueLength {
            get {
                if (p_IsDisposed) return 0;
                return p_Queue.Count + p_CurrentlyExecuting;
            }
        }

        public long CurrentlyExecuting {
            get { return p_CurrentlyExecuting; }
        }

        public bool IsActive {
            get { return QueueLength > 0; }
        }

        private void ProcessQueue() {
            while (!p_IsDisposed) {
                p_Notifier.WaitOne(300);

                T obj = default(T);
                bool exec = false;
                if (p_Queue.Count > 0) {
                    lock (p_Queue) {
                        // Now that we have an exclusive lock on the queue, it could be empty
                        if (p_Queue.Count > 0) {
                            System.Threading.Interlocked.Increment(ref p_CurrentlyExecuting);
                            // Ensure that CurrentlyExecuting + QueueLength <> 0
                            obj = p_Queue.Dequeue();
                            exec = true;
                        }
                    }
                }

                if (exec) {
                    try {
                        p_Action(obj);
                    } catch (Exception ex) {
                        if (!p_IsDisposed) {
                            if (OnException != null) {
                                OnException(ex);
                            }
                        }
                    } finally {
                        System.Threading.Interlocked.Decrement(ref p_CurrentlyExecuting);
                        if (obj is IDisposable) ((IDisposable)obj).Dispose();
                    }
                }
            }
        }


        public void Dispose() {
            p_IsDisposed = true;
            if (p_Pool != null) {
                p_Pool.Dispose();
            }
            p_Queue = null;
            p_Pool = null;
        }
    }
}